/*
 * cpu-adpll.c - Driver of CPU ADPLL
 *
 * Copyright (C) 2016-2018, LomboTech Co.Ltd.
 * Author: lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <mach/csp.h>
#include <mach/debug.h>
#include <linux/delay.h>
#include "cpu_adpll.h"

#if defined(CONFIG_LOMBO_CPU_PRCM)
void csp_cpu_init(void __iomem *base)
{
	reg_cpu_ctrl_cpu_clk_sel_t clk_sel;
	void __iomem *clk_addr = base + CLK_SRC_OFFSET;

	clk_sel.val = readl(clk_addr);
	clk_sel.bits.src = 0;
	writel(clk_sel.val, clk_addr);

	clk_sel.val = readl(clk_addr);
	clk_sel.bits.en = 1;
	writel(clk_sel.val, clk_addr);
}
#elif defined(CONFIG_LOMBO_CPU_FREQ_AVS)
void csp_cpu_init(void __iomem *base)
{
}
#else
/* Set CPU adpll in normal model */
void csp_cpu_init(void __iomem *base)
{
	reg_cpu_ctrl_adpll_tune0_t tune0;
	reg_cpu_ctrl_cpu_clk_sel_t clk_sel;
	void __iomem *clk_addr = base + CLK_SRC_OFFSET;
	void __iomem *tune0_addr = base + TUNE0_OFFSET;

	clk_sel.val = readl(clk_addr);
	clk_sel.bits.src = 0;
	writel(clk_sel.val, clk_addr);

	clk_sel.val = readl(clk_addr);
	clk_sel.bits.en = 0;
	writel(clk_sel.val, clk_addr);

	tune0.val = readl(tune0_addr);
	tune0.bits.tune4 = 1;
	writel(tune0.val, tune0_addr);

	tune0.val = readl(tune0_addr);
	tune0.bits.tune3 = 1;
	tune0.bits.tune2 = 1;
	tune0.bits.auto_adrange = 1;
	writel(tune0.val, tune0_addr);

	tune0.val = readl(tune0_addr);
	tune0.bits.tune1 = 31;
	writel(tune0.val, tune0_addr);

	tune0.val = readl(tune0_addr);
	tune0.bits.tune0 = 0;
	writel(tune0.val, tune0_addr);

	clk_sel.val = readl(clk_addr);
	clk_sel.bits.en = 1;
	writel(clk_sel.val, clk_addr);

	clk_sel.val = readl(clk_addr);
	clk_sel.bits.src = 1;
	writel(clk_sel.val, clk_addr);
}
#endif


static void wait_for_adpll_lock(void __iomem *base)
{
/* fpga need to disabled */
#if !(defined(CONFIG_ARCH_LOMBO_N7V5_FPGA) || \
	defined(CONFIG_ARCH_LOMBO_N5V1_FPGA) || \
	defined(CONFIG_ARCH_LOMBO_N9V3_FPGA))

	reg_cpu_ctrl_adpll_stat_t adpll_status;
	u32 reg_val;

	do {
		adpll_status.val = readl(base + STAT_OFFSET);
		reg_val = adpll_status.bits.flock & adpll_status.bits.plock;
	} while (reg_val != 1);

#endif
}

int csp_set_cpu_rate(void __iomem *base, unsigned int freq)
{
	int err = 0;
	reg_cpu_ctrl_adpll_fac_t adpll_fac;
	reg_cpu_ctrl_cpu_clk_sel_t clk_sel;

	clk_sel.val     = readl(base + CLK_SRC_OFFSET);
	clk_sel.bits.en = 1;

	adpll_fac.val   = readl(base + 0x80);

	if ((freq >= 240 * 1000 * 1000) && (freq <= 1200 * 1000 * 1000)) {
		adpll_fac.bits.n = freq / 24000000;
		clk_sel.bits.src_div = 0;
	} else if ((freq >= 120 * 1000 * 1000) && (freq < 240 * 1000 * 1000)) {
		adpll_fac.bits.n     = (freq * 2) / 24000000;
		clk_sel.bits.src_div = 1;
	} else if ((freq >= 60 * 1000 * 1000) && (freq < 120 * 1000 * 1000)) {
		adpll_fac.bits.n     = (freq * 4) / 24000000;
		clk_sel.bits.src_div = 2;
	} else {
		pr_err("invalid freq: %u\n", freq);
		err = __LINE__;
		goto out;
	}

	writel(adpll_fac.val, base + 0x80);

	writel(clk_sel.val, base + CLK_SRC_OFFSET);

	wait_for_adpll_lock(base);

out:
	return err;
}

#if !defined(CONFIG_LOMBO_CPU_FREQ_AVS)
/* return rate in kHz */
unsigned int csp_get_cpu_rate(void __iomem *base)
{
	unsigned int adpll_rate = 0;
	unsigned int cpu_rate = 0;
	unsigned int src_div = 0;
	reg_cpu_ctrl_adpll_fac_t adpll_fac;
	reg_cpu_ctrl_cpu_clk_sel_t clk_sel;

	clk_sel.val   = readl(base + CLK_SRC_OFFSET);
	adpll_fac.val = readl(base + FAC_OFFSET);
#ifdef CONFIG_ARCH_LOMBO_N7V1
	adpll_rate = (24 / (adpll_fac.bits.pre_div + 1))
				* adpll_fac.bits.n * 1000;
#else
	adpll_rate = 24 * adpll_fac.bits.n * 1000;
#endif
	if (clk_sel.bits.src_div == 0)
		src_div = 1;
	else if (clk_sel.bits.src_div == 1)
		src_div = 2;
	else if ((clk_sel.bits.src_div == 2) || (clk_sel.bits.src_div == 3))
		src_div = 4;

	cpu_rate = adpll_rate / src_div;

	return cpu_rate;
}
#else

u32 cpu_offset[14] = {
	RST_SCTRL_OFFSET, RST_CCTRL_OFFSET, CTRL_R_OFFSET, GENER_REG_OFFSET,
	CLK_CNT_CTRL_OFFSET, HFEOSC_CNT_OFFSET, MODULE_CNT_OFFSET,
	CLK_SRC_OFFSET, FAC_OFFSET, TUNE0_OFFSET, TUNE1_OFFSET, STAT_OFFSET,
	STAT_OFFSET,
};

unsigned int csp_get_cpu_rate(void __iomem *base)
{
	unsigned int return_rate = 0;
	static u64 hfeosc_cnt, module_cnt, rate;
	static u32 reg_val[14];
	static int i;

	writel(1, base + CLK_CNT_CTRL_OFFSET);
	usleep_range(10, 11);
	writel(0, base + CLK_CNT_CTRL_OFFSET);

	hfeosc_cnt = readl(base + HFEOSC_CNT_OFFSET);
	module_cnt = readl(base + MODULE_CNT_OFFSET);
	if (hfeosc_cnt == 0) {
		for (i = 0; i < 14; i++)
			reg_val[i] = readl(base + cpu_offset[i]);

		for (i = 0; i < 14; i++)
			PRT_ERR("addr: 0x%8p, val: 0x%08x\n",
					base + cpu_offset[i], reg_val[i]);

		PRT_ERR("hfeosc_cnt = %lld, module_cnt = %lld\n",
				hfeosc_cnt, module_cnt);
	}
	WARN_ON(hfeosc_cnt == 0);

	rate = module_cnt * 24 * 1000;
	do_div(rate, hfeosc_cnt);
	return_rate = (unsigned int)rate;

	return return_rate;
}
#endif

void csp_set_cpu_clk_src_sel(u32 clk_src)
{
	reg_cpu_ctrl_cpu_clk_sel_t clk_sel;

	clk_sel.val = readl((u32 *)VA_CPU_CTRL_CPU_CLK_SEL);
	clk_sel.bits.src = clk_src;
	writel(clk_sel.val, (u32 *)VA_CPU_CTRL_CPU_CLK_SEL);
}

